﻿using Microsoft.Win32;
using Nes.Core.Buss;
using Nes.Core.Mappers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Runtime.Intrinsics.X86;
using System.Text;
using System.Threading.Tasks;

namespace Nes.Core.PPUS
{
    /// <summary>
    /// 图像分成了 32 x 30 = 960 个 tile
    /// 每个 tile 在 name table 占前 960 字节
    /// 
    /// </summary>
    public class PPU
    {
        public PPU(IBus bus, IMapper mapper, Action<Byte[]> onFrame, CPU cpu)
        {
            CPU = cpu;
            Bus = bus;
            this.mapper = mapper;
            oamMemory = new Byte[0x100];
            Ctrl = new PPUCtrl();
            Ctrl.Data = 0x00;
            Mask = new PPUMask();
            Mask.Data = 0x00;
            Status = new PPUStatus();
            //Status.Data = 0b1010_0000;
            Status.Data = 0x00;
            frame = 0;
            OAMAddr = 0x00;
            nmiDelay = 0;
            pixels = new Byte[256 * 240];
            SecondaryOam = new SecondaryOamInfo[8];
            scanLine = 240;
            cycles = 340;
            spritePixels = new Byte[256]; // 处理边缘问题 + 8
            this.onFrame = onFrame;
        }

        #region Public
        public void Clock()
        {
            // https://www.nesdev.org/w/images/default/4/4f/Ppu.svg
            if (scanLine == 261 
                && cycles == 339
                && (frame & 0b1) == 1
                && Mask.ShowBackOrSprite()
                )
            {
                UpdateCycle();
            }
            UpdateCycle();
            if (!Mask.ShowBackground && !Mask.ShowSprite)
                //if (!Mask.ShowAllNotBackSprite())
            {
                return;
            }
            if(scanLine > -1 && scanLine < 240)
            {
                // 可见部分 scanLine [0, 239]
                if (cycles == 1)
                {
                    ClearSecondaryOam();
                }
                if (cycles == 65)
                {
                    EvalSprite();
                }
                if (cycles > 0 && cycles < 257)
                {
                    // [1, 256]
                    ShiftBackground();
                    RenderPixel();
                    FetchTileReleatedData();
                }
                if (cycles == 256)
                {
                    IncrementVerticalPosition();
                }
                if (cycles == 257)
                {
                    CopyHorizontalBits();
                }
                if (cycles == 257)
                {
                    FetchSprite();
                }
                if (cycles > 320 && cycles < 337)
                {
                    ShiftBackground();
                    FetchTileReleatedData();
                }
            }
            if(scanLine == 261)
            {
                // scanLine[261, 261]
                if (cycles > 0 && cycles < 257)
                {
                    ShiftBackground();
                    FetchTileReleatedData();
                }
                if (cycles == 256)
                {
                    IncrementVerticalPosition();
                }
                if (cycles == 257)
                {
                    CopyHorizontalBits();
                }
                if (cycles == 280)
                {
                    CopyVerticalBits();
                }
                if (cycles > 320 && cycles < 337)
                {
                    // [321, 336]
                    ShiftBackground();
                    FetchTileReleatedData();
                }
            }
        }

        private void CopyVerticalBits()
        {
            //  this.register.v = (this.register.v & 0b1000_0100_0001_1111) | (this.register.t & ~0b1000010000011111) & 0x7FFF;
            V = (V & 0x841F) | (T & ~0x841F) & 0x7FFF;
        }

        private void FetchSprite()
        {
            if (!Mask.ShowSprite)
            {
                return;
            }
            Array.Fill(spritePixels, Byte.MinValue);
            for(var i = SecondaryOam.Length - 1;  i > -1; i--)
            {
                var sprite = SecondaryOam[i];
                if (sprite.Y >= 0xEF)
                {
                    continue;
                }
                var behind = (sprite.Attributes & PPUConst.SpriteAttr_PRIORITY) > 0;
                var isZero = sprite.IsZero;
                var isFlipH = (sprite.Attributes & PPUConst.SpriteAttr_FLIP_H) > 0;
                var isFlipV = (sprite.Attributes & PPUConst.SpriteAttr_FLIP_V) > 0;
                Int32 addr;
                if (Ctrl.SpriteSize == PPUConst.SpriteSize_8x8)
                {// 8*8
                    var baseAddr = Ctrl.SpritePatternTableAddress + (sprite.TileIndex << 4);
                    var offset = isFlipV ? (7 - scanLine + sprite.Y) : (scanLine - sprite.Y);
                    addr = baseAddr + offset;
                }
                else
                {//8*16
                    var baseAddr = (((sprite.TileIndex & 0x01) > 0) ? 0x1000 : 0x0000) + ((sprite.TileIndex & 0xFE) << 4);
                    var offset = isFlipV ? (15 - scanLine + sprite.Y) : (scanLine - sprite.Y);
                    addr = baseAddr + offset % 8 + offset / 8 * 16;
                }
                var tileL = Bus.ReadByte(addr);
                var tileH = Bus.ReadByte(addr + 8);

                for(var j = 0; j < 8; j++)
                {
                    var b = isFlipH ? (0x01 << j) : (0x80 >> j);
                    var index = (((tileL & b) > 0) ? 1 : 0)
                        | (((tileH & b) > 0) ? 1 : 0) << 1
                        | (((sprite.Attributes & PPUConst.SpriteAttr_PALETTE_L) > 0) ? 1 : 0) << 2
                        | (((sprite.Attributes & PPUConst.SpriteAttr_PALETTE_H) > 0) ? 1 : 0) << 3;
                    if(sprite.X + j > spritePixels.Length - 1 || index % 4 == 0 
                        && ((spritePixels[sprite.X + j] & PPUConst.SpritePixel_PALETTE) % 4) > 0)
                    {
                        continue;
                    }
                    spritePixels[sprite.X + j] =
                        (Byte)(index | (behind ? PPUConst.SpritePixel_BEHIND_BG : 0) | (isZero ? PPUConst.SpritePixel_ZERO : 0));
                }
            }
        }

        /// <summary>
        /// hori(v)=hori(t)
        /// </summary>
        private void CopyHorizontalBits()
        {
            // this.register.v = (this.register.v & 0b1111101111100000) | (this.register.t & ~0b1111101111100000) & 0x7FFF;
            V = (V & 0xFBE0) | (T & ~0xFBE0) & 0x7FFF;
        }

        /// <summary>
        /// Inc.vert(v)horz(v)
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        private void IncrementVerticalPosition()
        {
            if((V & 0x7000) != 0x7000)
            {
                V += 0x1000;
            }
            else
            {
                V &= ~0x7000;
                var y = (V & 0x03E0) >> 5;
                // 0b11101
                if (y == 29)
                {
                    y = 0;
                }
                // 0b11111
                else if(y == 31)
                {
                    y = 0;
                    // 0b0000_1000_0000_0000
                    // 0b0000_1000_0000_0000
                    V ^= 0x0800;
                }
                else
                {
                    y += 1;
                }
                V = V & ~0x03E0 | y << 5;
            }
        }

        private void IncrementHorizontalPosition()
        {
            if((V & 0x1F) == 31)
            {
                V &= ~(0x1F);
                V ^= 0x0400;
            }
            else
            {
                V += 1;
            }
        }
        /// <summary>
        /// BG
        /// msbits
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        private void FetchTileReleatedData()
        {
            if (!Mask.ShowBackground)
            {
                return;
            }
            // 8 个一组
            // 0 1 3 5 7  
            // 0 IncHorPosition
            // 1 NameTable
            // 3 AttributeTable
            // 5 BGTileLowByte
            // 7 BGTileHeightByte
            switch(cycles & 7)
            {
                case 0:
                    IncrementHorizontalPosition();
                    break;
                case 1:
                    LoadBackgrand();
                    FetchNameTable();
                    break;
                case 3:
                    FetchAttributeTable();
                    break;
                case 5:
                    FetchLowBackgrandTileTable();
                    break;
                case 7:
                    FetchHeightBackgrandTileTable();
                    break;
            }

        }

        private void FetchHeightBackgrandTileTable()
        {
            var addr = Ctrl.BackgroundPatternTableAddress + LatchReg_NameTable * 16 + ((V >> 12) & 0X07) + 8;
            LatchReg_HBackTailByte = Bus.ReadByte(addr);
        }

        private void FetchLowBackgrandTileTable()
        {
            var addr = Ctrl.BackgroundPatternTableAddress + LatchReg_NameTable * 16 + ((V >> 12) & 0X07);
            LatchReg_LBackTailByte = Bus.ReadByte(addr);
        }

        private void FetchAttributeTable()
        {
            var addr = 0x23C0
                | (V & 0x0C00)
                | ((V >> 4) & 0x38)
                | ((V >> 2) & 0x07);
            var right = (V & 0x02) > 0;
            var bottom = (V & 0x40) > 0;
            var offset = (right ? 0x02 : 0) | (bottom ? 0x04 : 0);
            LatchReg_Arrtibute = (Byte)(Bus.ReadByte(addr) >> offset & 0x03);
        }

        private void FetchNameTable()
        {
            var addr = 0x2000 | (V & 0X0FFF);
            LatchReg_NameTable = Bus.ReadByte(addr);
        }

        private void LoadBackgrand()
        {
            RegShift_LBackTailByte |= LatchReg_LBackTailByte;
            RegShift_HBackTailByte |= LatchReg_HBackTailByte;
            RegShift_LBackAttrByte |= ((LatchReg_Arrtibute & 1) > 0) ? 0xFF : 0;
            RegShift_HBackAttrByte |= ((LatchReg_Arrtibute & 2) > 0) ? 0xFF : 0;
        }

        private void RenderPixel()
        {
            var x = cycles - 1;
            var y = scanLine;
            if(x > 10 && x < 50 && y > 180 && y < 195 && Frame > 100 && Frame < 200)
            {

            }
            var offset = 0x8000 >> X;
            var paletteIndex = ((RegShift_LBackTailByte & offset) > 0 ? 1 : 0)
                                | ((RegShift_HBackTailByte & offset) > 0 ? 1 : 0) << 1
                                | ((RegShift_LBackAttrByte & offset) > 0 ? 1 : 0) << 2
                                | ((RegShift_HBackAttrByte & offset) > 0 ? 1 : 0) << 3;

            var spriteIndex = spritePixels[x] & PPUConst.SpritePixel_PALETTE;
            var isTransparentSprite = spriteIndex % 4 == 0 || !Mask.ShowSprite;
            var isTransparentBackground = paletteIndex % 4 == 0 || !Mask.ShowBackground;
            var bassAddr = 0x3F00;
            if (isTransparentBackground)
            {
                if (!isTransparentSprite)
                {
                    bassAddr = 0x3F10 + spriteIndex;
                }
            }
            else
            {
                if (isTransparentSprite)
                {
                    bassAddr = 0x3F00 + paletteIndex;
                }
                else
                {
                    if ((spritePixels[x] & PPUConst.SpritePixel_ZERO) > 0)
                    {
                        if ((!Mask.ShowBackground || !Mask.ShowSprite)
                            || (x >=0 && x <= 7 && (!Mask.ShowSpriteLeft8px || !Mask.ShowBackgroundLeft8px)) 
                            || x == 255)
                        {

                        }
                        else
                        {
                            Status.Sprite0Hit = true;
                        }
                    }
                    bassAddr = (spritePixels[x] & PPUConst.SpritePixel_BEHIND_BG) > 0 ? (0x3F00 + paletteIndex) : (0x3F10 + spriteIndex);
                }
            }
            pixels[x + y * 256] = Bus.ReadByte(bassAddr);
        }

        private void ShiftBackground()
        {
            if (!Mask.ShowBackground)
            {
                return;
            }
            RegShift_LBackTailByte <<= 1;
            RegShift_HBackTailByte <<= 1;
            RegShift_LBackAttrByte <<= 1;
            RegShift_HBackAttrByte <<= 1;
        }

        /// <summary>
        /// 从oam内存中读取Sprite 信息
        /// </summary>
        private void EvalSprite()
        {
            if (!Mask.ShowSprite)
            {
                return;
            }
            var sprintCount = 0;
            for (int i = 0; i < 64; i++)
            {
                var ptr = i * 4;
                var data = oamMemory[ptr];
                if(scanLine < data || (scanLine >= data + Ctrl.SpriteSize))
                {
                    // 跳过不在扫描线范围内的精灵
                    continue;
                }
                if(sprintCount >= 8)
                {
                    // 每行最多8个精灵
                    Status.SpriteOverflow = true;
                    break;
                }
                var oamInfo = SecondaryOam[sprintCount++];
                oamInfo.SetInto(data, oamMemory[ptr + 1], oamMemory[ptr + 2], oamMemory[ptr + 3]);
                oamInfo.IsZero = i == 0;
            }
        }

        /// <summary>
        /// 清理辅助的 精灵单元 每个扫描线最多8个
        /// </summary>
        private void ClearSecondaryOam()
        {
            if (!Mask.ShowSprite)
            {
                return;
            }
            //Array.Fill(SecondaryOam, new SecondaryOamInfo { Attributes = 0xFF, TileIndex = 0xFF, X = 0xFF, Y = 0xFF });
            for(var i = 0; i < SecondaryOam.Length; i++)
            {
                SecondaryOam[i] = new SecondaryOamInfo { Attributes = 0xFF, TileIndex = 0xFF, X = 0xFF, Y = 0xFF };
            }
        }

        private void UpdateCycle()
        {
            if(Status.VBlankStarted && Ctrl.NMIEnabled && nmiDelay-- == 0)
            {
                CPU.NMI();
            }
            cycles++;
            if(cycles > 340)
            {
                cycles = 0;
                scanLine++;
                if(scanLine > 261)
                {
                    scanLine = 0;
                    frame++;
                    onFrame?.Invoke(pixels);
                }
            }
            if(scanLine == 241 && cycles == 1)
            {
                Status.VBlankStarted = true;
                if (Ctrl.NMIEnabled)
                {
                    nmiDelay = 15;
                }
            }
            if (scanLine == 261 && cycles == 1)
            {
                Status.VBlankStarted = false;
                Status.SpriteOverflow = false;
                Status.Sprite0Hit = false;
            }
            if(Mask.ShowBackAndSprite())
            {
                mapper.PPUClockHandle(scanLine, cycles);
            }
        }

        public Byte CPURead(Int32 address)
        {
            switch (address)
            {
                case PPUConst.CtrlAddr:
                    return Ctrl.Data;
                case PPUConst.MaskAddr:
                    return Mask.Data;
                case PPUConst.StatusAddr:
                    return ReadStatus();
                case PPUConst.OAMADDRAddr:
                    return 0;
                case PPUConst.OAMDATAAddr:
                    return oamMemory[OAMAddr];
                case PPUConst.PPUSCROLLAddr:
                    return 0;
                case PPUConst.PPUADDRAddr:
                    return 0;
                case PPUConst.PPUDATAAddr:
                    return ReadPPUData();
                default:
                    return 0;
            }
        }
        public void CPUWrite(Int32 address, Byte data)
        {
            // ?
            previousData = (Byte)(data & 0x1f);

            switch (address)
            {
                case PPUConst.CtrlAddr:
                    WriteCtrl(data);
                    break;
                case PPUConst.MaskAddr:
                    Mask.Data = data;
                    break;
                case PPUConst.StatusAddr:
                    // PPUSTATUS
                    break;
                case PPUConst.OAMADDRAddr:
                    // OAMADDR
                    OAMAddr = data;
                    break;
                case PPUConst.OAMDATAAddr:
                    // OMADATA
                    oamMemory[OAMAddr++] = data;
                    break;
                case PPUConst.PPUSCROLLAddr:
                    WriteScroll(data);
                    break;
                case PPUConst.PPUADDRAddr:
                    WritePPUAddr(data);
                    break;
                case PPUConst.PPUDATAAddr:
                    WritePPUData(data);
                    break;
            }
        }
        #endregion


        public IBus Bus { get; private set; }
        public ICPU CPU { get; private set; }
        public IMapper mapper { get; private set; }

        /// <summary>
        /// 辅助OAM信息
        /// </summary>
        public SecondaryOamInfo[] SecondaryOam;

        private Action<Byte[]> onFrame;

        /// <summary>
        /// 
        /// </summary>
        private Byte[] oamMemory;
        /// <summary>
        /// Nmi 中断延迟时钟
        /// </summary>
        private Int32 nmiDelay;
        /// <summary>
        /// 像素在调色盘的偏移量矩阵
        /// </summary>
        private Byte[] pixels;
        /// <summary>
        /// 精灵
        /// </summary>
        private Byte[] spritePixels;
        /// <summary>
        /// 渲染的帧数
        /// </summary>
        private Int32 frame;
        public Int32 Frame { get { return frame; } }

        /// <summary>
        /// 扫描线 初始值240
        /// </summary>
        private Int32 scanLine;
        /// <summary>
        /// 第几列 初始值340
        /// </summary>
        private Int32 cycles;
        /// <summary>
        /// 
        /// </summary>
        private Byte readBuffer;
        private Byte previousData;

        #region Register
        /// <summary>
        /// Common name: PPUCTRL
        /// Description: PPU control register
        /// Access: write
        /// </summary>
        private PPUCtrl Ctrl;
        /// <summary>
        /// Common name: PPUMASK
        /// Description: PPU mask register
        /// Access: write
        /// </summary>
        private PPUMask Mask;
        /// <summary>
        /// Common name: PPUSTATUS
        /// <para></para>
        /// Description: PPU status register
        /// <para></para>
        /// Access: read
        /// </summary>
        private PPUStatus Status;
        /// <summary>
        /// Common name: OAMADDR
        /// <para></para>
        /// Description: OAM address port
        /// <para></para>
        /// Access: write
        /// </summary>
        private Byte OAMAddr;
        ///// <summary>
        ///// Common name: OAMDATA
        ///// <para></para>
        ///// Description: OAM data port
        ///// <para></para>
        ///// Access: read, write
        ///// </summary>
        //private Byte OAMData;
        ///// <summary>
        ///// Common name: PPUSCROLL
        ///// Description: PPU scrolling position register
        ///// Access: write twice
        ///// </summary>
        //private Byte PPUScroll;
        ///// <summary>
        ///// Common name: PPUADDR
        ///// Description: PPU address register
        ///// Access: write twice
        ///// </summary>
        //private Byte PPUAddr;
        ///// <summary>
        ///// Common name: PPUDATA
        ///// Description: PPU data port
        ///// Access: read, write
        ///// </summary>
        //private Byte PPUData;
        #endregion

        #region Shift Register  位移寄存器
        private Int128 RegShift_LBackTailByte = 0;
        private Int128 RegShift_HBackTailByte = 0;
        private Int128 RegShift_LBackAttrByte = 0;
        private Int128 RegShift_HBackAttrByte = 0;
        #endregion

        #region Latch
        private Byte LatchReg_LBackTailByte = 0;
        private Byte LatchReg_HBackTailByte = 0;
        private Byte LatchReg_Arrtibute = 0;
        private Byte LatchReg_NameTable = 0;
        #endregion

        #region Internal Register
        /// <summary>
        /// During rendering, used for the scroll position. Outside of rendering, used as the current VRAM address.
        /// <para></para>
        /// 渲染期间，用于滚动位置。渲染之外，用作当前VRAM地址。
        /// </summary>
        private int V;
        /// <summary>
        /// During rendering, specifies the starting coarse-x scroll for the next scanline and the starting y scroll for the screen. 
        /// Outside of rendering, holds the scroll or VRAM address before transferring it to v.
        /// <para></para>
        /// 在渲染期间，指定下一扫描线的开始coarse-x滚动和屏幕的开始y滚动。 
        /// <para></para>
        /// 在渲染之外，在将其传输到v之前保存滚动或VRAM地址。
        /// </summary>
        private int T;
        /// <summary>
        /// The fine-x position of the current scroll, used during rendering alongside v.
        /// <para></para>
        /// 当前滚动x的位置，在渲染时与v一起使用
        /// </summary>
        private int X;
        /// <summary>
        /// Toggles on each write to either PPUSCROLL or PPUADDR, indicating whether this is the first or second write. Clears on reads of PPUSTATUS. Sometimes called the 'write latch' or 'write toggle'.
        /// <para></para>
        /// 每次写 PPUSCROLL/PPUADDR 时触发， 表示时第一次写还是第二次写；
        /// 当读PPUSTATUS 时清除
        /// </summary>
        private int W;
        #endregion

        #region CPURead
        private Byte ReadPPUData()
        {
            var data = Bus.ReadByte(V);
            if (V < 0x3EFF)
            {
                (data, readBuffer) = (readBuffer, data);
            }
            else
            {
                readBuffer = Bus.ReadByte(V - 0x1000);
            }
            V += Ctrl.VramIncrementStepSize;
            V &= 0x7FFF;
            return data;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private Byte ReadStatus()
        {
            var data = Status.Data | previousData;
            Status.VBlankStarted = false;
            W = 0;
            return (Byte)data;
        }
        #endregion
        
        #region CPUWrite
        private void WriteScroll(Byte data)
        {
            if (W == 0)
            {
            //t: ....... ...ABCDE < -d: ABCDE...
            //x: FGH < -d: .....FGH
            //w:                  < -1
                T = T & 0xFFE0 | data >> 3;
                X = data & 0x07;
                W = 1;
            }
            else
            {
            //t: FGH..AB CDE..... < -d: ABCDEFGH
            //w:                  < -0
                T = T & 0x0C1F | (data & 0x07) << 12 | (data & 0xF8) << 2;
                W = 0;
            }
        }

        private void WritePPUAddr(Byte data)
        {
            // https://www.nesdev.org/wiki/PPU_scrolling
            if (W == 0)
            {
                // 高8位
                // 0x3F 0b0011_1111 (data < 64)
                // 
                //t: .CDEFGH........ < -d: ..CDEFGH
                //       <unused> < -d: AB......
                //t: Z.............. < -0(bit Z is cleared)
                //w:                  < -1
                T = T & 0x80FF | (data & 0x3F) << 8;
                W = 1;
            }
            else
            {
                // 低8位
                //t: ....... ABCDEFGH < -d: ABCDEFGH
                //v: < ...all bits...> < -t: < ...all bits...>
                //w:                  < -0
                V = T = T & 0xFF00 | data;
                W = 0;
            }
        }

        private void WritePPUData(Byte data)
        {
            this.Bus.WriteByte(V, data);
            V += Ctrl.VramIncrementStepSize;
        }

        private void WriteCtrl(Byte data)
        {
            Ctrl.Data = data;
            // TODO 最低两位是 nametable 的 索引
            T = T & 0XF3FF | (data & 0x03) << 10;
        }
        #endregion

        internal void DMACopy(Byte[] data)
        {
            for(var i = 0; i < 256; i++)
            {
                oamMemory[(i + OAMAddr) & 0xFF] = data[i];
            }
        }
    }

    public static class PPUConst
    {
        public const Int32 CtrlAddr = 0x2000;
        public const Int32 MaskAddr = 0x2001;
        public const Int32 StatusAddr = 0x2002;
        public const Int32 OAMADDRAddr = 0x2003;
        public const Int32 OAMDATAAddr = 0x2004;
        public const Int32 PPUSCROLLAddr = 0x2005;
        public const Int32 PPUADDRAddr = 0x2006;
        public const Int32 PPUDATAAddr = 0x2007;

        public const Int32 SpriteAttr_PALETTE_L = 1;
        public const Int32 SpriteAttr_PALETTE_H = 2;
        public const Int32 SpriteAttr_PRIORITY = 32;
        public const Int32 SpriteAttr_FLIP_H = 64;
        public const Int32 SpriteAttr_FLIP_V = 128;
        public const Int32 SpriteSize_8x8 = 8;
        public const Int32 SpriteSize_8x16 = 16;

        public const Int32 SpritePixel_PALETTE = 63;
        public const Int32 SpritePixel_BEHIND_BG = 64;
        public const Int32 SpritePixel_ZERO = 128;
    } 

    /// <summary>
    /// 每个PaletteTable 16 字节 分为两个部分 各8个字节
    /// 第一个部分的每个bit控制 每个tile的对应像素的第一位
    /// 第二个部分的每个bit控制 每个tile的对应像素的第二位
    /// 00 透明或者按背景的颜色
    /// 01 像素的颜色索引为1。
    /// 10 像素的颜色索引为2。
    /// 11 像素的颜色索引为3。
    /// PaletteTable 分成 2组 每组256个 0x0000-0x0fff 0x1000-0x1fff
    /// 地址
    /// 
    /// 
    /// </summary>
    public class PaletteTable
    {
        private static Byte[][] PaletteDic2 = [
            [0xFF, 0x80, 0x80, 0x80],
            [0xFF, 0x00, 0x00, 0xBB],
            [0xFF, 0x37, 0x00, 0xBF],
            [0xFF, 0x84, 0x00, 0xA6],
            [0xFF, 0xBB, 0x00, 0x6A],
            [0xFF, 0xB7, 0x00, 0x1E],
            [0xFF, 0xB3, 0x00, 0x00],
            [0xFF, 0x91, 0x26, 0x00],
            [0xFF, 0x7B, 0x2B, 0x00],
            [0xFF, 0x00, 0x3E, 0x00],
            [0xFF, 0x00, 0x48, 0x0D],
            [0xFF, 0x00, 0x3C, 0x22],
            [0xFF, 0x00, 0x2F, 0x66],
            [0xFF, 0x00, 0x00, 0x00],
            [0xFF, 0x05, 0x05, 0x05],
            [0xFF, 0x05, 0x05, 0x05],
            [0xFF, 0xC8, 0xC8, 0xC8],
            [0xFF, 0x00, 0x59, 0xFF],
            [0xFF, 0x44, 0x3C, 0xFF],
            [0xFF, 0xB7, 0x33, 0xCC],
            [0xFF, 0xFF, 0x33, 0xAA],
            [0xFF, 0xFF, 0x37, 0x5E],
            [0xFF, 0xFF, 0x37, 0x1A],
            [0xFF, 0xD5, 0x4B, 0x00],
            [0xFF, 0xC4, 0x62, 0x00],
            [0xFF, 0x3C, 0x7B, 0x00],
            [0xFF, 0x1E, 0x84, 0x15],
            [0xFF, 0x00, 0x95, 0x66],
            [0xFF, 0x00, 0x84, 0xC4],
            [0xFF, 0x11, 0x11, 0x11],
            [0xFF, 0x09, 0x09, 0x09],
            [0xFF, 0x09, 0x09, 0x09],
            [0xFF, 0xFF, 0xFF, 0xFF],
            [0xFF, 0x00, 0x95, 0xFF],
            [0xFF, 0x6F, 0x84, 0xFF],
            [0xFF, 0xD5, 0x6F, 0xFF],
            [0xFF, 0xFF, 0x77, 0xCC],
            [0xFF, 0xFF, 0x6F, 0x99],
            [0xFF, 0xFF, 0x7B, 0x59],
            [0xFF, 0xFF, 0x91, 0x5F],
            [0xFF, 0xFF, 0xA2, 0x33],
            [0xFF, 0xA6, 0xBF, 0x00],
            [0xFF, 0x51, 0xD9, 0x6A],
            [0xFF, 0x4D, 0xD5, 0xAE],
            [0xFF, 0x00, 0xD9, 0xFF],
            [0xFF, 0x66, 0x66, 0x66],
            [0xFF, 0x0D, 0x0D, 0x0D],
            [0xFF, 0x0D, 0x0D, 0x0D],
            [0xFF, 0xFF, 0xFF, 0xFF],
            [0xFF, 0x84, 0xBF, 0xFF],
            [0xFF, 0xBB, 0xBB, 0xFF],
            [0xFF, 0xD0, 0xBB, 0xFF],
            [0xFF, 0xFF, 0xBF, 0xEA],
            [0xFF, 0xFF, 0xBF, 0xCC],
            [0xFF, 0xFF, 0xC4, 0xB7],
            [0xFF, 0xFF, 0xCC, 0xAE],
            [0xFF, 0xFF, 0xD9, 0xA2],
            [0xFF, 0xCC, 0xE1, 0x99],
            [0xFF, 0xAE, 0xEE, 0xB7],
            [0xFF, 0xAA, 0xF7, 0xEE],
            [0xFF, 0xB3, 0xEE, 0xFF],
            [0xFF, 0xDD, 0xDD, 0xDD],
            [0xFF, 0x11, 0x11, 0x11],
            [0xFF, 0x11, 0x11, 0x11],
            [0xFF, 0x00, 0x00, 0x00],
        ];

        private static Color[] PaletteDic = [
            Color.FromArgb(255,0x80, 0x80, 0x80),
            Color.FromArgb(255,0x00, 0x00, 0xBB),
            Color.FromArgb(255,0x37, 0x00, 0xBF),
            Color.FromArgb(255,0x84, 0x00, 0xA6),
            Color.FromArgb(255,0xBB, 0x00, 0x6A),
            Color.FromArgb(255,0xB7, 0x00, 0x1E),
            Color.FromArgb(255,0xB3, 0x00, 0x00),
            Color.FromArgb(255,0x91, 0x26, 0x00),
            Color.FromArgb(255,0x7B, 0x2B, 0x00),
            Color.FromArgb(255,0x00, 0x3E, 0x00),
            Color.FromArgb(255,0x00, 0x48, 0x0D),
            Color.FromArgb(255,0x00, 0x3C, 0x22),
            Color.FromArgb(255,0x00, 0x2F, 0x66),
            Color.FromArgb(255,0x00, 0x00, 0x00),
            Color.FromArgb(255,0x05, 0x05, 0x05),
            Color.FromArgb(255,0x05, 0x05, 0x05),
            Color.FromArgb(255,0xC8, 0xC8, 0xC8),
            Color.FromArgb(255,0x00, 0x59, 0xFF),
            Color.FromArgb(255,0x44, 0x3C, 0xFF),
            Color.FromArgb(255,0xB7, 0x33, 0xCC),
            Color.FromArgb(255,0xFF, 0x33, 0xAA),
            Color.FromArgb(255,0xFF, 0x37, 0x5E),
            Color.FromArgb(255,0xFF, 0x37, 0x1A),
            Color.FromArgb(255,0xD5, 0x4B, 0x00),
            Color.FromArgb(255,0xC4, 0x62, 0x00),
            Color.FromArgb(255,0x3C, 0x7B, 0x00),
            Color.FromArgb(255,0x1E, 0x84, 0x15),
            Color.FromArgb(255,0x00, 0x95, 0x66),
            Color.FromArgb(255,0x00, 0x84, 0xC4),
            Color.FromArgb(255,0x11, 0x11, 0x11),
            Color.FromArgb(255,0x09, 0x09, 0x09),
            Color.FromArgb(255,0x09, 0x09, 0x09),
            Color.FromArgb(255,0xFF, 0xFF, 0xFF),
            Color.FromArgb(255,0x00, 0x95, 0xFF),
            Color.FromArgb(255,0x6F, 0x84, 0xFF),
            Color.FromArgb(255,0xD5, 0x6F, 0xFF),
            Color.FromArgb(255,0xFF, 0x77, 0xCC),
            Color.FromArgb(255,0xFF, 0x6F, 0x99),
            Color.FromArgb(255,0xFF, 0x7B, 0x59),
            Color.FromArgb(255,0xFF, 0x91, 0x5F),
            Color.FromArgb(255,0xFF, 0xA2, 0x33),
            Color.FromArgb(255,0xA6, 0xBF, 0x00),
            Color.FromArgb(255,0x51, 0xD9, 0x6A),
            Color.FromArgb(255,0x4D, 0xD5, 0xAE),
            Color.FromArgb(255,0x00, 0xD9, 0xFF),
            Color.FromArgb(255,0x66, 0x66, 0x66),
            Color.FromArgb(255,0x0D, 0x0D, 0x0D),
            Color.FromArgb(255,0x0D, 0x0D, 0x0D),
            Color.FromArgb(255,0xFF, 0xFF, 0xFF),
            Color.FromArgb(255,0x84, 0xBF, 0xFF),
            Color.FromArgb(255,0xBB, 0xBB, 0xFF),
            Color.FromArgb(255,0xD0, 0xBB, 0xFF),
            Color.FromArgb(255,0xFF, 0xBF, 0xEA),
            Color.FromArgb(255,0xFF, 0xBF, 0xCC),
            Color.FromArgb(255,0xFF, 0xC4, 0xB7),
            Color.FromArgb(255,0xFF, 0xCC, 0xAE),
            Color.FromArgb(255,0xFF, 0xD9, 0xA2),
            Color.FromArgb(255,0xCC, 0xE1, 0x99),
            Color.FromArgb(255,0xAE, 0xEE, 0xB7),
            Color.FromArgb(255,0xAA, 0xF7, 0xEE),
            Color.FromArgb(255,0xB3, 0xEE, 0xFF),
            Color.FromArgb(255,0xDD, 0xDD, 0xDD),
            Color.FromArgb(255,0x11, 0x11, 0x11),
            Color.FromArgb(255,0x11, 0x11, 0x11),
        ];

        private static Color defaultColor = Color.FromArgb(255,0,0,0);
        public unsafe static Color GetColor(Int32 px)
        {
            if(px > 63)
            {
                return defaultColor;
            }
            fixed(Color* ptr = PaletteDic)
            {
                return (*(ptr + px));
            }
            //return PaletteDic[px];
        }

        public unsafe static Byte[] GetColor2(Int32 px)
        {
            if (px > 63)
            {
                return PaletteDic2[^1];
            }
            fixed (Byte[]* ptr = PaletteDic2)
            {
                return (*(ptr + px));
            }
            //return PaletteDic[px];
        }
    }
}
